#include "pwzd.h"
#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "hello/hello.h"
#include "cli_handler.h"
#include "multicast.h"

#define forever for(;;)
#define handle_error(param) do {\
	perror(param); exit(EXIT_FAILURE);\
	} while (0);
#define for_each(i, node, hash)  for (i = 0; i < (hash)->size; ++i) for (node = ((hash)->nodes)[i]; node; node = node->next)

int clifd;                  // do nasłuchu CLI
int client_sock_fd    = 1;  // do rozmów z CLI
int multicast_sock_fd = 1;  // do rozsyłania hello

int verbose = 0;

struct client_info *others       = NULL; // korzeń drzewa

size_t tab_len;
struct sockaddr_in groupSock;

HASHTBL *my_interests;     // zainteresowanie -> NULL                | string -> void
HASHTBL **tab;             // #interwałów -> (adres -> NULL)         | int -> (string -> void)
HASHTBL *clients;          // adres -> client_info                   | string -> client_info
HASHTBL *interestToClient; // zainteresowanie -> (adres -> NULL)     | string -> string

int main(int argc, char* argv[])
{
    if (argc > 1) {
        if (!strcmp(argv[1], "-v")) {
            verbose = 1;
        }
    }

    setup();

    // wczyta z pliku początkowe zainteresowania.
    load_config("pwzd.conf");

    multicast_sock_fd = setup_multicastfd();
    clifd             = setup_unixfd();

    printf("------------------------------------------\n");
    printf("| pwzd: started                          |\n");
    printf("------------------------------------------\n");

    main_loop();

    printf("------------------------------------------\n");
    printf("| pwzd: finished                         |\n");
    printf("------------------------------------------\n");

    close(client_sock_fd);
    close(multicast_sock_fd);
    return 0;
}

void load_config(char * filename)
{
//    FILE * fd = fopen(filename, "r");
//    if (!fd) {
//        perror(filename);
//        exit(EXIT_FAILURE);
//    }
//    char interest[1500];

//    int bytes_read;
//    while((bytes_read = fscanf(fd, "%[^\n]", interest))) {
//        interest[bytes_read] = '\0';
//        hashtbl_res_insert(my_interests, interest,  NULL);
//    }
}

void setup()
{
    int i;

    // inicjalizacja haszy:
    tab_len = 6;
    tab = calloc(tab_len, sizeof(HASHTBL *));
    for (i = 0; i < tab_len; ++i)
        tab[i] = hashtbl_create(16, NULL);

    my_interests     = hashtbl_create(16, NULL);
    interestToClient = hashtbl_create(16, NULL);
    clients          = hashtbl_create(16, NULL);

    // inicjalizacja adresu multicast
    memset((char *) &groupSock, 0, sizeof(groupSock));
    groupSock.sin_family = AF_INET;
    groupSock.sin_addr.s_addr = inet_addr("224.0.0.213");
    groupSock.sin_port = htons(6969);
}

void main_loop()
{
// wybierz fd na który coś przyszło
    fd_set readfds, master;
    FD_ZERO(&readfds);
    FD_ZERO(&master);
    FD_SET(multicast_sock_fd, &master);
    FD_SET(clifd, &master);

    int max = clifd > multicast_sock_fd ? clifd : multicast_sock_fd;

    send_hello();

    forever {

        readfds = master;

        struct timeval tv;
        tv.tv_sec  = DEFAULT_INTERVAL;
        tv.tv_usec = 0;

        if (select(max+1, &readfds, NULL, NULL, &tv) == -1) {
            perror("select");
            exit(4);
        }

        if (!tv.tv_sec && !tv.tv_usec) {
            send_hello();
            clean_old();
        }

        if (FD_ISSET(multicast_sock_fd, &readfds)) {
            read_multicastfd(multicast_sock_fd);
        }

        else if (FD_ISSET(clifd, &readfds)) {
            read_unixfd(clifd);
        }

//        else
//            printf("pwzd: Timed out\n");
    }
}

void clean_old()
{
    hash_size i, j;
    struct hashnode_s *node, *n;
    char * address;
    struct client_info *info;

    // usuń wszystkie elementy tab[tab_len-1]
    for_each(i, node, tab[tab_len-1]) {

        address = node->key;
        info = (struct client_info*)hashtbl_get(clients, address);
        printf("%s too old (intervals = %d, i = %d), dropping.\n", address, info->intervals, (int)i);

        HASHTBL *interests = info->interests;

        for_each(j, n, interests) {
            HASHTBL *tbl = (HASHTBL*)hashtbl_get(interestToClient, n->key);
            if (tbl) {
                hashtbl_res_remove(tbl, address);
            }
        }
        hashtbl_res_remove(clients, address);
        hashtbl_destroy(interests);
    }

    hashtbl_destroy(tab[tab_len-1]);

    // przesuń elementy tablicy w prawo
    for (i = tab_len-1; i > 0; --i) {
        tab[i] = tab[i-1];
        // każdemu elementowi z danego wiersza zwiększ licznik interwałów
        for_each(j, node, tab[i]){
                address = node->key;
                info = (struct client_info *)hashtbl_get(clients, address);
                info->intervals += 1;
        }
    }
    tab[0] = hashtbl_create(16, NULL);
}
